#include <assert.h>
#include <imbus.h>
#include <stdio.h>
#include <string.h>

#include "im_internals.h"
#define NO_TEST_OBJECT_MAIN
#include "test_object.h"
#include "test_object.c"

static const IMUInt16 utf16str [] = {0x4e00, 0x4e01, 0x3400, 0xd840, 0xdc21, 0x3007, 0x0040, 0xd869, 0xdea9, 0x002d, 0};

void test_serialize ()
{
    IMStream        *stream;
    IMValue         *value;
    IMObjectArray   *array;
    Point           *point;
    Circle          *circle;
    IMAttribute     *attr;
    IMText          *text;
    IMLookupTable   *table;
    IMValueArray    *varray;
    IMString        *string;
    IMPropList      *proplist;
    IMPropList      *subproplist;
    IMProperty      *prop;
    IMIOChannel     *channel_out;
    IMEventRoles    *roles;
    IMComponentInfo *info;

    IMInt32          int32dim [10] = {1,2,3,4,5,6,7,8,9,10};

    IMSize n_flags = 3;
    IMLookupTableFlag flags[] = {
        IM_LOOKUP_TABLE_SHOW_TITLE,
        IM_LOOKUP_TABLE_SHOW_CURSOR,
        IM_LOOKUP_TABLE_VERTICAL_SHRINKABLE
    };

    channel_out = im_io_channel_unix_new (1);

    stream = im_text_stream_new ();

    value  = im_value_new ();

    array  = im_object_array_new ();

    point  = point_new (15, 30);

    circle = circle_new (24, 32, 48);

    im_value_set_static_c_string (value, "Hello World!\n");

    im_object_array_append (array, im_object_clone (value));

    im_value_set_static_c_string (value, "我们是祖国的花朵 lalalala...");

    im_object_array_append (array, im_object_clone (value));

    im_value_set_double (value, 3.1415926);

    im_object_array_insert (array, 1, value);

    im_object_array_append (array, point);
    im_object_array_prepend (array, circle);

    attr = im_attr_language_new ("zh-cn");
    im_attribute_set_range (attr, 0, 1);
    im_object_array_append (array, attr);
    attr = im_attr_font_family_new ("Times New Roman");
    im_attribute_set_range (attr, 1, 2);
    im_object_array_append (array, attr);
    attr = im_attr_font_style_new (IM_ATTR_FONT_STYLE_ITALIC);
    im_attribute_set_range (attr, 2, 3);
    im_object_array_append (array, attr);
    attr = im_attr_font_weight_new (IM_ATTR_FONT_WEIGHT_BOLD);
    im_attribute_set_range (attr, 3, 4);
    im_object_array_append (array, attr);
    attr = im_attr_font_size_new (12);
    im_attribute_set_range (attr, 4, 5);
    im_object_array_append (array, attr);
    attr = im_attr_foreground_new (0, 100, 400);
    im_attribute_set_range (attr, 5, 6);
    im_object_array_append (array, attr);
    attr = im_attr_background_new (400, 200, 100);
    im_attribute_set_range (attr, 7, 8);
    im_object_array_append (array, attr);
    attr = im_attr_underline_new (IM_ATTR_UNDERLINE_WAVE);
    im_attribute_set_range (attr, 8, 9);
    im_object_array_append (array, attr);
    attr = im_attr_underline_color_new (800, 400, 200);
    im_attribute_set_range (attr, 9, 10);
    im_object_array_append (array, attr);
    attr = im_attr_strikethrough_new ();
    im_attribute_set_range (attr, 10, 11);
    im_object_array_append (array, attr);
    attr = im_attr_strikethrough_color_new (1200, 800, 600);
    im_attribute_set_range (attr, 11, 12);
    im_object_array_append (array, attr);
    attr = im_attr_highlight_new ();
    im_attribute_set_range (attr, 12, 13);
    im_object_array_append (array, attr);
    attr = im_attr_reverse_new ();
    im_attribute_set_range (attr, 13, 14);
    im_object_array_append (array, attr);
    attr = im_attr_string_new (0);
    im_attr_string_set_static (attr, "A static attribute string.");
    im_attribute_set_range (attr, 14, 15);
    im_object_array_append (array, attr);
    attr = im_attr_object_new (circle);
    im_attribute_set_range (attr, 15, 16);
    im_object_array_append (array, attr);

    string = im_string_new_static ("This is a static string.");
    im_object_array_append (array, string);
    string = im_object_clone (string);
    im_string_append_str (string, " hahaha.", -1);
    im_object_array_append (array, string);
    string = im_string_get_sub_string (string, 8, 9);
    im_object_array_append (array, string);

    im_stream_put_void (stream);
    im_stream_put_bool (stream, TRUE);
    im_stream_put_int32 (stream, -12345);
    im_stream_put_uint32 (stream, 0x1000);
    im_stream_put_int64 (stream, ((IMInt64)1) << 63);
    im_stream_put_uint64 (stream, ((IMUInt64)1) << 63);
    im_stream_put_c_string (stream, "Hello\tworld!\n", -1);
    im_stream_put_byte_array (stream, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26);

    im_stream_put_object (stream, array);

    im_stream_put_int32_array (stream, int32dim, 10);

    table = im_lookup_table_new_full (12345,
                                      IM_LOOKUP_TABLE_TTBLTR,
                                      4, 4, flags, n_flags);

    im_lookup_table_set_title (table, im_text_new_from_utf8_string ("It's a Lookup Table.", -1));
    im_lookup_table_set_scroll_info (table, 10, 3);

    text = im_text_new_from_utf8_string ("Hello world! It's an UTF-8 string, 大家好𠁎㐀一.", -1);

    im_text_add_attribute (text, 0, 5, im_attr_underline_new (IM_ATTR_UNDERLINE_SINGLE));
    im_text_add_attribute (text, 6, 5, im_attr_font_style_new (IM_ATTR_FONT_STYLE_ITALIC));
    im_text_add_attribute (text, 13, 20, im_attr_strikethrough_new ());
    im_text_add_attribute (text, 34, 10, im_attr_foreground_new (10, 20, 30));

    im_lookup_table_set_candidate (table, 4,
            im_candidate_new_full (
                text,
                im_text_new_from_utf8_string ("5", -1),
                0,
                point));

    im_stream_put_object (stream, text);

    text = im_text_new_from_utf16_string (utf16str, -1);
    im_text_add_attribute (text, 3, 1, im_attr_highlight_new ());
    im_text_add_attribute (text, 6, 1, im_attr_highlight_new ());

    im_lookup_table_set_candidate (table, 8,
            im_candidate_new_full (
                text,
                im_text_new_from_utf8_string ("9", -1),
                0,
                circle));

    im_stream_put_object (stream, text);

    im_object_set_attachment (table, "A Text Label", im_text_new_from_utf8_string ("This is a LookupTable.", -1));
    im_object_set_attachment (table, "A Circle", circle_new (111, 222, 333));
    im_object_set_attachment (table, "A String", im_string_new_from_text_utf8 (text));

    im_stream_put_object (stream, table);

    varray = im_value_array_new ();
    im_value_array_append (varray, im_value_new_char ('a'));
    im_value_array_append (varray, im_value_new_uchar ('A'));
    im_value_array_append (varray, im_value_new_int16 (-16));
    im_value_array_append (varray, im_value_new_uint16 (16));
    im_value_array_append (varray, im_value_new_int32 (-32));
    im_value_array_append (varray, im_value_new_uint32 (32));
    im_value_array_append (varray, im_value_new_int64 (-64));
    im_value_array_append (varray, im_value_new_int64 (-1234567890123LL));
    im_value_array_append (varray, im_value_new_uint64 (1234567890123LL));
    im_value_array_append (varray, im_value_new_double (1.2345));
    im_value_array_append (varray, im_value_new_c_string ("Dyanmic string!"));
    im_value_array_append (varray, im_value_new_static_c_string ("Static string!"));
    im_value_array_append (varray, im_value_new_take_c_string (strdup ("Strdup string!")));

    im_value_array_append_object (varray, point);
    im_value_array_append_object (varray, circle);
    im_value_array_append_object (varray, im_value_new_double (3.1415926));
    im_value_array_append_object (varray, im_value_new_static_c_string ("Nested string."));
    im_value_array_append_object (varray, im_value_new_object (point));
    im_value_array_append_object (varray, im_string_new_from_printf ("Address of ValueArray obj: %p", varray));

    im_stream_put_object (stream, varray);

    proplist = im_prop_list_new ();

    im_prop_list_append (proplist,
            im_property_new_full (IM_PROPERTY_NORMAL,
                                  im_string_new_static ("/Root/Node1"),
                                  im_string_new_static ("Icon1"),
                                  im_text_new_from_utf8_string ("第一个Node", -1),
                                  im_text_new_from_utf8_string ("A tip", -1),
                                  TRUE, TRUE, IM_PROPERTY_UNCHECKED));

    im_prop_list_append (proplist,
            im_property_new_full (IM_PROPERTY_NORMAL,
                                  im_string_new_static ("/Root/Node2"),
                                  im_string_new_static ("Icon2"),
                                  im_text_new_from_utf8_string ("第二个Node", -1),
                                  0,
                                  TRUE, FALSE, IM_PROPERTY_CHECKED));

    im_prop_list_append (proplist,
            im_property_new_full (IM_PROPERTY_NORMAL,
                                  im_string_new_static ("/Root/Node3"),
                                  im_string_new_static ("Icon3"),
                                  im_text_new_from_utf8_string ("第三个Node", -1),
                                  im_text_new_from_utf8_string ("A tip", -1),
                                  FALSE, TRUE, IM_PROPERTY_INCONSISTENT));

    im_prop_list_append (proplist,
            im_property_new_full (IM_PROPERTY_TOGGLE,
                                  im_string_new_static ("/Root/Node4"),
                                  im_string_new_static ("Icon4"),
                                  im_text_new_from_utf8_string ("第四个Node", -1),
                                  0,
                                  FALSE, FALSE, IM_PROPERTY_UNCHECKED));

    prop = im_prop_list_get (proplist, "/Root/Node3");
    subproplist = im_prop_list_new ();
    im_prop_list_append (subproplist,
            im_property_new_full (IM_PROPERTY_RADIO,
                                  im_string_new_static ("/Root/Node3/SubNode1"),
                                  im_string_new_static ("Icon5"),
                                  im_text_new_from_utf8_string ("第三个Node第一个子Node", -1),
                                  0,
                                  TRUE, TRUE, IM_PROPERTY_UNCHECKED));
    im_prop_list_append (subproplist,
            im_property_new_full (IM_PROPERTY_RADIO,
                                  im_string_new_static ("/Root/Node3/SubNode2"),
                                  im_string_new_static ("Icon6"),
                                  im_text_new_from_utf8_string ("第三个Node第二个子Node", -1),
                                  0,
                                  TRUE, TRUE, IM_PROPERTY_UNCHECKED));

    im_property_set_sub_props (prop, subproplist);

    im_stream_put_object (stream, proplist);

    roles = im_event_roles_new ();

    im_event_roles_set (roles, IM_EVENT_REGISTER_COMPONENT, IM_EVENT_ROLE_PRODUCER);
    im_event_roles_set (roles, IM_EVENT_DEREGISTER_COMPONENT, IM_EVENT_ROLE_PRODUCER);
    im_event_roles_set (roles, IM_EVENT_PROCESS_KEY_EVENT, IM_EVENT_ROLE_CONSUMER);
    im_event_roles_set (roles, IM_EVENT_UPDATE_SPOT_LOCATION, IM_EVENT_ROLE_OBSERVER);

    info = im_component_info_new_full (im_string_new_static ("org.imbus.foo"),
                                       im_string_new_static ("Test Component"),
                                       im_string_new_static ("Just a test"),
                                       im_string_new_static ("file://org.imbus.foo/foo.png"),
                                       roles);
    im_component_info_set_unique_id (info, 12345);
    im_component_info_set_name (info, im_string_new_static ("中文名字"), "zh-cn");
    im_component_info_set_description (info, im_string_new_static ("中文说明"), "zh-cn");

    im_stream_put_object (stream, info);

    string = im_string_new_static ("quit");
    im_stream_put_object (stream, string);
    im_object_unref (string);

    im_stream_write (stream, channel_out, 0, 0);

    im_object_unref (array);
    im_object_unref (varray);
    im_object_unref (table);
    im_object_unref (proplist);
    im_object_unref (stream);
    im_object_unref (channel_out);
    im_object_unref (info);
}

#define GET_FUNDAMENTAL_VALUE(stream,strtype,type,fmt)  \
    do {                                                \
        type val;                                       \
        if (im_stream_get_##strtype (stream, &val)) {   \
            printf ("Success :");                       \
            printf (fmt, val);                          \
            printf ("\n");                              \
        } else {                                        \
            printf ("Fail\n");                          \
        }                                               \
    } while (0)

#define GET_FUNDAMENTAL_VALUE_ARRAY(stream,strtype,type,fmt,num)  \
    do {                                                \
        IMSize i;                                       \
        type *val = (type *) im_malloc(num * sizeof (type));     \
        if (im_stream_get_##strtype (stream, val, num)) {   \
            printf ("Success :");                       \
            for (i=0; i<num; ++i) printf (fmt, val[i]); \
            printf ("\n");                              \
        } else {                                        \
            printf ("Fail\n");                          \
        }                                               \
        im_free (val);                                  \
    } while (0)

void test_deserialize ()
{
    IMStream        *stream;
    IMStream        *new_stream;
    IMPointer        obj;
    IMType           type;
    IMSize           size;
    IMIOChannel     *channel_in;
    IMIOChannel     *channel_out;

    stream = im_text_stream_new ();
    new_stream = im_text_stream_new ();

    channel_in = im_io_channel_unix_new (0);
    channel_out = im_io_channel_unix_new (1);

    if (!im_stream_read (stream, channel_in, 0, 0)) {
        fprintf (stderr, "Failed to load stream.\n");
        im_object_unref (stream);
        im_object_unref (new_stream);
        return;
    }

    type = im_stream_get_data_type (stream);
    size = im_stream_get_data_size (stream);

    while (type != IM_TYPE_INVALID) {
        printf ("Get data: %s %d\n", im_type_get_name (type), size);
        switch (type) {
            case IM_TYPE_VOID:
                if (im_stream_get_void (stream))
                    printf ("Success.\n");
                else
                    printf ("Fail\n");
                break;
            case IM_TYPE_CHAR:
                GET_FUNDAMENTAL_VALUE(stream,char,IMChar,"%d");
                break;
            case IM_TYPE_UCHAR:
                GET_FUNDAMENTAL_VALUE(stream,uchar,IMUChar,"%u");
                break;
            case IM_TYPE_BOOL:
                GET_FUNDAMENTAL_VALUE(stream,bool,IMBool,"%d");
                break;
            case IM_TYPE_INT16:
                GET_FUNDAMENTAL_VALUE(stream,int16,IMInt16,"%d");
                break;
            case IM_TYPE_UINT16:
                GET_FUNDAMENTAL_VALUE(stream,uint16,IMUInt16,"%u");
                break;
            case IM_TYPE_INT32:
                GET_FUNDAMENTAL_VALUE(stream,int32,IMInt32,"%d");
                break;
            case IM_TYPE_UINT32:
                GET_FUNDAMENTAL_VALUE(stream,uint32,IMUInt32,"%u");
                break;
            case IM_TYPE_INT64:
                GET_FUNDAMENTAL_VALUE(stream,int64,IMInt64,"%" IM_IMINT64_FORMAT);
                break;
            case IM_TYPE_UINT64:
                GET_FUNDAMENTAL_VALUE(stream,uint64,IMUInt64,"%" IM_IMUINT64_FORMAT);
                break;
            case IM_TYPE_DOUBLE:
                GET_FUNDAMENTAL_VALUE(stream,double,IMDouble,"%.8f");
                break;
            case IM_TYPE_C_STRING:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,c_string,IMChar,"%c",size);
                break;
            case IM_TYPE_BYTE_ARRAY:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,byte_array,IMChar,"%c",size);
                break;
            case IM_TYPE_BOOL_ARRAY:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,bool_array,IMBool,"%d,",size);
                break;
            case IM_TYPE_INT16_ARRAY:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,int16_array,IMInt16,"%d,",size);
                break;
            case IM_TYPE_UINT16_ARRAY:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,uint16_array,IMUInt16,"%d,",size);
                break;
            case IM_TYPE_INT32_ARRAY:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,int32_array,IMInt32,"%d,",size);
                break;
            case IM_TYPE_UINT32_ARRAY:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,uint32_array,IMUInt32,"%d,",size);
                break;
            case IM_TYPE_INT64_ARRAY:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,int64_array,IMInt64,"%" IM_IMINT64_FORMAT ",",size);
                break;
            case IM_TYPE_UINT64_ARRAY:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,uint64_array,IMUInt64,"%" IM_IMUINT64_FORMAT ",",size);
                break;
            case IM_TYPE_DOUBLE_ARRAY:
                GET_FUNDAMENTAL_VALUE_ARRAY(stream,double_array,IMDouble,"%.8f,",size);
                break;
            default:
                if (IM_TYPE_IS_OBJECT (type)) {
                    obj = im_object_new (type);
                    im_object_ref (obj);
                    if (im_stream_get_object (stream, obj)) {
                        printf ("Success\n");
                        im_stream_put_object (new_stream, obj);
                    } else {
                        printf ("Fail\n");
                    }
                    im_object_unref (obj);
                } else {
                    printf ("Unknown data type.\n");
                    im_object_unref (stream);
                    return;
                }
        }

        type = im_stream_get_data_type (stream);
        size = im_stream_get_data_size (stream);
    }

    im_stream_write (new_stream, channel_out, 0, 0);

    im_stream_rewind (stream);
    type = im_stream_get_data_type (stream);
    size = im_stream_get_data_size (stream);

    while (type != IM_TYPE_INVALID) {
        printf ("Skip data: %s %d ", im_type_get_name (type), size);
        if (im_stream_skip_data (stream))
            printf ("Success\n");
        else
            printf ("Fail\n");

        type = im_stream_get_data_type (stream);
        size = im_stream_get_data_size (stream);
    }

    im_object_unref (stream);
    im_object_unref (new_stream);
    im_object_unref (channel_in);
    im_object_unref (channel_out);
}

int main (int argc, char *argv [])
{
    im_init (&argc, &argv);

    (void) TYPE_POINT;
    (void) TYPE_CIRCLE;

#if IM_ENABLE_DEBUG
    _im_slice_print_debug_info();
#endif

    if (argc > 1) {
        if (strcmp (argv [1], "-o") == 0) {
            test_serialize ();
#if IM_ENABLE_DEBUG
            _im_slice_print_debug_info();
#endif
            return 0;
        }
        else if (strcmp (argv [1], "-i") == 0) {
            test_deserialize ();
#if IM_ENABLE_DEBUG
            _im_slice_print_debug_info();
#endif
            return 0;
        }
    }

    printf ("Usage: test-text-stream [option]\n"
            "Option:\n"
            "  -o   serialize\n"
            "  -i   deserialize\n");

    return -1;
}
/*
vi:ts=4:nowrap:ai:expandtab
*/
